﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Text;
using System.Web;
using System.Reflection;
using System.Data;

namespace DataLayer.Controller
{
    /// <summary>
    /// Clase que provee métodos genéricos para el acceso a datos
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TDataContext"></typeparam>
    [System.ComponentModel.DataObject]
    public class GenericController<TEntity, TDataContext> where TDataContext : DataContext
    {
        #region Properties
        private static TDataContext _dataContext;

        /// <summary>
        /// Objeto que mantiene el contexto del acceso a datos
        /// </summary>
        protected static TDataContext DataContext
        {
            get
            {
                //We are in a web app, use a request scope
                if (HttpContext.Current != null)
                {
                    TDataContext _dataContext = (TDataContext)HttpContext.Current.Items["_dataContext"];
                    if (_dataContext == null)
                    {
                        _dataContext = Activator.CreateInstance<TDataContext>();
                        HttpContext.Current.Items.Add("_dataContext", _dataContext);
                    }
                    return _dataContext;
                }
                else
                {
                    //If this is not a web app then just create a datacontext
                    //which will have the same lifespan as the app itself
                    //This is only really to support unit tests and should not
                    //be used in any production code. A better way to use this
                    //code with unit tests is to mock the HttpContext
                    if (_dataContext == null)
                    {
                        _dataContext = Activator.CreateInstance<TDataContext>();
                    }
                    return _dataContext;
                }

            }
        }
        protected static ITable EntityTable
        {
            get
            {
                return DataContext.GetTable(EntityType);
            }
        }
        protected static string EntityName
        {
            get
            {
                return EntityType.Name;
            }
        }
        protected static Type EntityType
        {
            get
            {
                return typeof(TEntity);
            }
        }
        protected static string TableName
        {
            get
            {
                var att = (from a in EntityType.GetCustomAttributes(false)
                           where a.GetType() == typeof(TableAttribute)
                           select a).FirstOrDefault();

                return att == null ? "" : ((TableAttribute)att).Name; ;
            }
        }
        public static DataLoadOptions LoadOptions
        {
            get
            {
                return DataContext.LoadOptions;
            }
            set
            {
                DataContext.LoadOptions = value;
            }
        }
        #endregion

        #region Helper methods
        protected static PropertyInfo[] GetColumns()
        {
            //Get all the properties of this entity which have a Column attribute
            //this is how Linq to Sql does its mapping.

            return (from p in EntityType.GetProperties()
             where
                  (from att in p.GetCustomAttributes(true)
                   where att.GetType() == typeof(ColumnAttribute)
                   select att).Count() > 0
             select p).ToArray<PropertyInfo>();
        }
        protected static PropertyInfo GetPrimaryKey()
        {   
            foreach (PropertyInfo pi in GetColumns())
            {
                foreach (object o in pi.GetCustomAttributes(true))
                {
                    if (o.GetType() == typeof(ColumnAttribute))
                    {
                        ColumnAttribute col = (ColumnAttribute)o;
                        if (col.IsPrimaryKey)
                            return pi;
                    }
                }
            }
            return null;
        }
        protected static int GetPrimaryKeyValue(TEntity entity)
        {
            PropertyInfo pi = GetPrimaryKey();
            return (int)pi.GetValue(entity, null);
        }
        protected static TEntity GetEntity(TEntity entity)
        {
            return GetEntity(GetPrimaryKeyValue(entity));
        }
        protected static void UpdateOriginalFromChanged(ref TEntity destination, TEntity source)
        {
            PropertyInfo[] propsToUpdate = GetColumns();

            //Update all the column properties using reflection
            foreach (PropertyInfo pi in propsToUpdate)
            {
                pi.SetValue(destination, pi.GetValue(source, null), null);
            }

        }
        #endregion

        #region Generic CRUD methods
        //---------------------Selects----------------------------------
        [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select)]
        protected static IQueryable<TEntity> SelectAllAsQuery()
        {
            return EntityTable.Cast<TEntity>();
        }
        
        [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select)]
        public static List<TEntity> SelectAll()
        {
            return SelectAllAsQuery().ToList();
        }
        
        [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select)]
        public static TEntity GetEntity(int id)
        {
            string query = string.Format("Select * from {0} where {1} = {2}", new object[] { TableName, GetPrimaryKey().Name, id });
            return DataContext.ExecuteQuery<TEntity>(query).FirstOrDefault();
        }

        //----------------------Insert------------------------------------
        [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Insert)]
        public static void Insert(TEntity entity)
        {
            Insert(entity, true);
        }
        public static void Insert(TEntity entity, bool submitChanges)
        {
            EntityTable.InsertOnSubmit(entity);
            if (submitChanges)
                DataContext.SubmitChanges();
        }

        //-----------------------Update-----------------------------------------
        [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Update)]
        public static void Update(TEntity entity)
        {
            Update(entity, true);
        }
        public static void Update(TEntity entity, bool submitChanges)
        {
            TEntity original = GetEntity(entity);
            UpdateOriginalFromChanged(ref original, entity);
            if (submitChanges)
                DataContext.SubmitChanges();
        }

        //----------------------Delete-------------------------------------------
        [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Delete)]
        public static void Delete(TEntity entity)
        {
            Delete(entity, true);
        }
        public static void Delete(TEntity entity, bool submitChanges)
        {
            TEntity delete = GetEntity(entity);
            EntityTable.DeleteOnSubmit(delete);
            if (submitChanges)
                DataContext.SubmitChanges();
        }
        #endregion

        public static void SubmitChanges()
        {
            DataContext.SubmitChanges();
        }

       
    }
}